// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using SnapObjects.Commons;

namespace SnapObjects.Data
{
    internal class PocoPropertyMeta : IPocoProperty
    {
        private readonly IPocoModelMeta _modelMeta;
  
        public PocoPropertyMeta(
            IPocoModelMeta modelMeta,
            int index,
            PropertyInfo propertyInfo)
        {
            _modelMeta = modelMeta;
            this.Index = index;
            this.Name = propertyInfo.Name;
            this.DataType = propertyInfo.PropertyType;
            this.PropertyInfo = propertyInfo;
            this.IsNullable = this.DataType.IsNullable();

            this.Attribute = this.CreatePropertyAttributeInfo(index, propertyInfo);

            this.HasConversion = this.Attribute.HasConversion;

            if (this.HasConversion)
            {
                this.ValueConverter = this.Attribute.ValueConverterInfo.ValueConverter;
            }
        }

        protected virtual PropertyAttributeInfo CreatePropertyAttributeInfo(int index, PropertyInfo propertyInfo)
        {
            return new PropertyAttributeInfo(index, propertyInfo);
        }

        public PropertyInfo PropertyInfo { get; }

        public PropertyAttributeInfo Attribute { get; }

        public bool IsEmbedded => this.Attribute.IsEmbedded;

        public Type EmbeddedType
        {
            get
            {
                Type embeddedType;

                if (this.IsContainerType)
                {
                    var Types = this.DataType.GenericTypeArguments;
                    embeddedType = Types[0];
                }
                else if (this.DataType.IsArray)
                {
                    embeddedType = this.DataType.GetElementType();
                }
                else
                {
                    embeddedType = this.DataType;
                }

                return embeddedType;
            }
        }

        public bool IsContainerType => this.DataType.IsArray || (this.DataType.IsGenericType &&
                                        (this.DataType.GetGenericTypeDefinition() == typeof(IList<>) ||
                                            this.DataType.GetGenericTypeDefinition() == typeof(ICollection<>) ||
                                                this.DataType.GetGenericTypeDefinition() == typeof(IEnumerable<>) ||
                                                    this.DataType.GetInterfaces().Contains(typeof(IEnumerable))));

        public int Index { get; }

        public string Name { get; }

        public virtual string MappedColumn => this.Attribute.SqlColumnDefined ? this.Attribute.SqlColumn.Column :
                                        (this.Attribute.ColumnDefined && this.Attribute.Column.NameDefined ?
                                            this.Attribute.Column.Name : this.Name);

        public virtual string ColumnName => this.Attribute.SqlColumnDefined ? this.Attribute.SqlColumn.Column :
                                        (this.Attribute.ColumnDefined && this.Attribute.Column.NameDefined ?
                                            this.Attribute.Column.Name : null);

        public string ColumnAlias => this.Attribute.SqlColumnDefined && this.Attribute.SqlColumn.ColumnAliasDefined ?
                                        this.Attribute.SqlColumn.ColumnAlias :
                                            (this.Attribute.SqlComputerDefined && this.Attribute.SqlComputer.AliasDefined ?
                                                this.Attribute.SqlComputer.Alias : null);

        public int ColumnOrder => this.Attribute.SqlColumnDefined ? this.Attribute.SqlColumn.Order :
                                            (this.Attribute.SqlComputerDefined ? this.Attribute.SqlComputer.Order : -1);

        public Type DataType { get; }

        public bool IsNullable { get; }

        public bool IsBindable => !this.Attribute.IsNotMapped && !this.Attribute.IsEmbedded;

        public bool IsForeign => this.IsForeignColumn();
        private bool IsForeignColumn()
        {
            var tableDefined = this.Attribute.SqlColumnDefined && this.Attribute.SqlColumn.TableAliasDefined;

            var isForeignColumn = false;

            if (!tableDefined)
            {
                return isForeignColumn;
            }

            if (String.IsNullOrWhiteSpace(_modelMeta.MappedTableAlias))
            {
                if (!String.IsNullOrEmpty(_modelMeta.MappedSchema))
                {
                    var fullTableName = _modelMeta.MappedSchema + "." + _modelMeta.MappedTable;

                    isForeignColumn = !fullTableName.Equals(
                        this.Attribute.SqlColumn.TableAlias, StringComparison.OrdinalIgnoreCase);

                    if (isForeignColumn)
                    {
                        var columnTable = _modelMeta.MappedSchema + "." + this.Attribute.SqlColumn.TableAlias;

                        isForeignColumn = !fullTableName.Equals(columnTable, StringComparison.OrdinalIgnoreCase);
                    }
                }
                else
                {
                    isForeignColumn = !_modelMeta.MappedTable.Equals(
                      this.Attribute.SqlColumn.TableAlias, StringComparison.OrdinalIgnoreCase);
                }
            }
            else
            {
                isForeignColumn = !_modelMeta.MappedTableAlias.Equals(
                    this.Attribute.SqlColumn.TableAlias, StringComparison.OrdinalIgnoreCase);
            }

            return isForeignColumn;
        }

        public bool IsCompute => this.Attribute.SqlComputerDefined;

        public bool IsNotMapped => this.Attribute.IsNotMapped;

        public bool IsKey => this.Attribute.IsKey;

        public bool IsIdentity => this.Attribute.IsIdentity;

        public bool IsConcurrencyCheck => this.Attribute.IsConcurrencyCheck;

        public bool IsJsonIgnore => this.Attribute.IsJsonIgnore;

        public bool IsXmlIgnore => this.Attribute.IsXmlIgnore; //Not best practices, wait for optimization

        public virtual SaveStrategy SaveStrategy => this.Attribute.PropertySave.SaveStrategy;

        public bool IsCascadeCreate => this.Attribute.IsEmbedded &&
                                    this.Attribute.ModelEmbedded != null &&
                                    this.Attribute.ModelEmbedded.CascadeCreate;

        public bool IsCascadeDelete => this.Attribute.IsEmbedded &&
                                    this.Attribute.ModelEmbedded != null &&
                                    this.Attribute.ModelEmbedded.CascadeDelete;

        public bool HasSetValueOperation => this.Attribute.IsEmbedded &&
                                    this.Attribute.HasSetValueOperation;

        public ISqlColumn SqlColumn => this.Attribute.SqlColumn;

        public ISqlCompute SqlCompute => this.Attribute.SqlComputer;

        public ISqlEmbedded SqlEmbedded => this.Attribute.SqlEmbedded;

        public IModelEmbedded ModelEmbedded => this.Attribute.ModelEmbedded;

        public IReadOnlyList<ISetValue> SetValueOperations => this.Attribute.SetValueOperations;

        public bool IsNotInWhere => this.Attribute.IsNotInWhere;

        public bool IsTimestamp => this.Attribute.IsTimestamp;

        public bool HasConversion { get; set; } 

        public ValueConverter ValueConverter { get; set; }

        public string TypeName =>
            this.Attribute.SqlColumnDefined && this.SqlColumn.TypeNameDefined ?
            this.Attribute.SqlColumn.TypeName :
            this.Attribute.ColumnDefined ? this.Attribute.Column.TypeName : null;
    }
}
